use moss_core::from_derive_input::FromDeriveInput;
use moss_core::from_derive_input::InputStructRef;
use proc_macro2::TokenStream;
use quote::quote;
use syn::DeriveInput;
use syn::Result;

pub struct FromAttrsImpl<'a> {
    pub input: InputStructRef<'a>,
}

impl<'a> FromDeriveInput<'a> for FromAttrsImpl<'a> {
    fn from_data_struct(input: InputStructRef<'a>) -> Result<Self> {
        Ok(Self { input })
    }
}

impl FromAttrsImpl<'_> {
    pub fn expand(self) -> Result<TokenStream> {
        let for_ident = self.input.ident;
        let (impl_generics, ty_generics, where_clause) = self.input.generics.split_for_impl();

        let mut init_token = quote!();
        let mut visit_token = quote!();
        let mut finish_token = quote!();
        for field in self.input.data_struct.fields.iter() {
            let field_ty = &field.ty;
            let field_ident = &field.ident;
            let field_name = field_ident
                .as_ref()
                .map(|t| t.to_string())
                .unwrap_or_default();
            init_token = quote! {
                #init_token
                let mut #field_ident = <#field_ty as moss::from_attrs::FromAttrs>::init();
            };
            visit_token = quote! {
                #visit_token

                let matched = <#field_ty as moss::from_attrs::FromAttrs>::visit_attr(&mut #field_ident, __attr, #field_name)?;
                if matched {
                    continue;
                }
            };
            finish_token = quote! {
                #finish_token
                #field_ident: <#field_ty as moss::from_attrs::FromAttrs>::finish(#field_ident, #field_name)?,
            };
        }

        let generated = quote! {
            impl #impl_generics TryFrom<&Vec<syn::Attribute>> for #for_ident #ty_generics #where_clause {
                type Error = syn::Error;

                fn try_from(value: &Vec<syn::Attribute>) -> syn::Result<Self> {
                    #init_token
                    if !value.is_empty() {
                        for __attr in value {
                            #visit_token
                        }
                    }
                    let result = Self {
                        #finish_token
                    };
                    Ok(result)
                }
            }
        };
        Ok(generated)
    }
}

pub fn expand(input: &DeriveInput) -> Result<TokenStream> {
    let from_attrs_impl = FromAttrsImpl::from_derive_input(input)?;
    from_attrs_impl.expand()
}
